using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace ProngHorn.Template
{
	/// <summary>
	/// This is the template processing class
	/// </summary>
	public class Template
	{
		#region Fields

		private readonly StringBuilder _internalTemplate = new StringBuilder();
		private bool _processFields;
		private bool _processProperties = true;
		private readonly string _regexPattern = "#{.*?}";

		#endregion Fields

		#region Constructors

		/// <summary>
		/// String based template processor
		/// </summary>
		/// <param name="template">The String with the template</param>
		public Template(String template)
		{
			_internalTemplate.Append(template);
		}

		/// <summary>
		/// String based template processor
		/// </summary>
		/// <param name="template">The String with the template</param>
		/// <param name="regExPattern">A cusotm regular expression pattern to apply to the template</param>
		public Template(String template, string regExPattern)
		{
			_internalTemplate.Append(template);
			_regexPattern = regExPattern;
		}

		#endregion Constructors

		#region Private Methods

		/// <summary>
		/// Returns the value.ToString() of a field in an object
		/// </summary>
		/// <param name="item">The object</param>
		/// <param name="fieldName">The field name</param>
		/// <returns> object.Field.ToString() </returns>
		private string getFieldValue(object item, string fieldName)
		{
			string[] name = getNameParts(fieldName);
			FieldInfo fieldInfo;
			object newItem = item;

			for (int i = 0; i < name.Length; i++)
			{
			    fieldInfo = newItem.GetType().GetField(name[i]);
			    if (fieldInfo != null)
			    {
			        newItem = fieldInfo.GetValue(newItem);
			    }
			}
			if (newItem != item && newItem != null)
			{
			    return newItem.ToString();
			}
			return string.Empty;
		}

		/// <summary>
		/// Apply a RegEx on the template and returns a MatchCollection
		/// </summary>
		/// <returns>MatchCollection</returns>
		private MatchCollection getMatchCollection()
		{
			return Regex.Matches(_internalTemplate.ToString(), _regexPattern, RegexOptions.None);
		}

		/// <summary>
		/// Given a regex patterns filters the match and return an array with all the parts of the template key
		/// </summary>
		/// <param name="regexMatch">The template key with all the upper and lower limit</param>
		/// <returns>String array</returns>
		private string[] getNameParts(string regexMatch)
		{
			string[] regexParts = _regexPattern.Replace(".*?", "-").Split('-');
			return regexMatch.Replace(regexParts[0], "").Replace(regexParts[1], "").Split('.');
		}

		/// <summary>
		/// Returns the value.ToString() of a property in an object
		/// </summary>
		/// <param name="item">The object</param>
		/// <param name="fieldName">The property name</param>
		/// <returns> object.Property.ToString() </returns>
		private string getPropertyValue(object item, string fieldName)
		{
			string[] name = getNameParts(fieldName);
			PropertyInfo propertyInfo;
			object newItem = item;

			for (int i = 0; i < name.Length; i++)
			{
			    propertyInfo = newItem.GetType().GetProperty(name[i]);
			    if (propertyInfo != null)
			    {
			        newItem = propertyInfo.GetValue(newItem, null);
			    }
			}
			if (newItem != item && newItem != null)
			{
			    return newItem.ToString();
			}
			return string.Empty;
		}

		/// <summary>
		/// Check if the object has Fields that correspond to the names of the matching template variables and replace the variables with a value.ToString() of the field
		/// </summary>
		/// <param name="item">The object to get the fields from</param>
		/// <param name="mc">A MatchCollection</param>
		private void processFields(object item, MatchCollection mc)
		{
			foreach (Match m in mc)
			{
			    string v = getFieldValue(item, m.Value);
			    if (v.Length > 0)
			    {
			        _internalTemplate.Replace(m.Value, v);
			    }
			}
		}

		/// <summary>
		/// Check if the object has Properties that correspond to the names of the matching template variables and replace the variables with a value.ToString() of the field
		/// </summary>
		/// <param name="item">The object to get the properties from</param>
		/// <param name="mc">A MatchCollection</param>
		private void processProperties(object item, MatchCollection mc)
		{
			foreach (Match m in mc)
			{
			    string v = getPropertyValue(item, m.Value);
			    _internalTemplate.Replace(m.Value, v);
			}
		}

		#endregion Private Methods

		#region Public Methods

		/// <summary>
		/// IEnumarable template, process a collection and apply the template to each item of the collection, usefull to write, lista and tables.
		/// </summary>
		/// <param name="list">The object, needs to implement IEnumerable</param>
		/// <param name="processFields">If <c>true</c> read all Fields for the object</param>
		/// <param name="processProperties">If <c>true</c> read all the Properties for the object</param>
		/// <returns>String containing the content of the collection formated with the template</returns>
		public string Process(IEnumerable list, bool processFields, bool processProperties)
		{
			_processFields = processFields;
			_processProperties = processProperties;

			string cachedTemplate = _internalTemplate.ToString();
			var sb = new StringBuilder();
			MatchCollection mc = getMatchCollection();

			foreach (Object item in list)
			{
			    _internalTemplate.Remove(0, _internalTemplate.Length);
			    _internalTemplate.Append(cachedTemplate);

			    if (_processFields)
			    {
			        this.processFields(item, mc);
			    }

			    if (_processProperties)
			    {
			        this.processProperties(item, mc);
			    }

			    sb.Append(_internalTemplate.ToString());
			}
			return sb.ToString();
		}

		/// <summary>
		/// Process a template with data from a single object, use the propeties of the object as the data containers.
		/// </summary>
		/// <param name="item">The object with the data</param>
		/// <param name="processFields">If <c>true</c> read all Fields for the object</param>
		/// <param name="processProperties">If <c>true</c> read all the Properties for the object</param>
		/// <returns>String with the processed template</returns>
		public string Process(Object item, bool processFields, bool processProperties)
		{
			var list = new List<object> {item};
			return Process(list, processFields, processProperties);
		}

		#endregion Public Methods
	}
}